import os
import pywintypes
import win32com.client as com
import numpy as np
import random
import csv
import Corflo_util as util
import Corflo_RM as RM


def runSimulation(params):

    link_groups = [
                {'MAINLINE': (1,),            'ONRAMP': (),             'OFFRAMP': (),          'DC': 1, 'VSL': (6,7,8,9,10)},
                {'MAINLINE': (2,),            'ONRAMP': (3,),           'OFFRAMP': (4,),        'DC': 2, 'VSL': (11,12,13,14,15,16)},
                {'MAINLINE': (5,),            'ONRAMP': (6,),           'OFFRAMP': (7,),        'DC': 3, 'VSL': (17,18,19,20,21,22)},
                {'MAINLINE': (8,20),          'ONRAMP': (),             'OFFRAMP': (9,),        'DC': 4, 'VSL': (23,24,25,26,27)},
                {'MAINLINE': (10,19),         'ONRAMP': (11,),          'OFFRAMP': (),          'DC': 5, 'VSL': (28,29,30,31,32,33)},
                {'MAINLINE': (12,),           'ONRAMP': (14,),          'OFFRAMP': (13,),       'DC': 6, 'VSL': (34,35,36,37,38,39)},
                {'MAINLINE': (15,10016),      'ONRAMP': (16,),          'OFFRAMP': (17,),       'DC': 7, 'VSL': (40,41,42,43,44,45)},
                {'MAINLINE': (18,),           'ONRAMP': (),             'OFFRAMP': (),          'DC': 8, 'VSL': (46,47,48,49,50)},
    ]
    
    ramps = {
                3: {'LINK_GROUP': 2, 'TYPE': 'ON', 'INPUT': 7, 'DC': 9, 'SC': 1, 'QC': 1, 'Qcap': 460},
                4: {'LINK_GROUP': 2, 'TYPE': 'OFF', 'DC': 10},
                6: {'LINK_GROUP': 3, 'TYPE': 'ON', 'INPUT': 9, 'DC': 11, 'SC': 2, 'QC': 2, 'Qcap': 460},
                7: {'LINK_GROUP': 3, 'TYPE': 'OFF', 'DC': 12},
                9: {'LINK_GROUP': 4, 'TYPE': 'OFF', 'DC': 13},
                11: {'LINK_GROUP': 5, 'TYPE': 'ON', 'INPUT': 11, 'DC': 14, 'SC': 3, 'QC': 3, 'Qcap': 420},
                14: {'LINK_GROUP': 6, 'TYPE': 'ON', 'INPUT': 13, 'DC': 16, 'SC': 4, 'QC': 4, 'Qcap': 300},
                13: {'LINK_GROUP': 6, 'TYPE': 'OFF', 'DC': 15},
                16: {'LINK_GROUP': 7, 'TYPE': 'ON', 'INPUT': 15, 'DC': 17, 'SC': 5, 'QC': 5, 'Qcap': 280},
                17: {'LINK_GROUP': 7, 'TYPE': 'OFF', 'DC': 18},
    }
    
    scenarios = [{'group': 0, 'link': 0, 'lane': (), 'coordinate': 0, 'startTime_sec': 60, 'endTime_sec': 60}]
    
    Nsec = len(link_groups)     # number of sections
    simResolution = 5           # No. of simulation steps per second
    stepTime_sec = 1 / simResolution
    Tdata_sec = 30.0            # Data sampling period
    Tctrl_sec = 30.0            # Control time interval
    randseed = round(random.uniform(1,1000))
    scenario = scenarios[params["scenario"]]      
    startTime_sec = scenario["startTime_sec"]
    endTime_sec = scenario["endTime_sec"]
    demands = params["demands"]
    vf = 100                    # The free flow speed of highway in km/h
    C = 12000                   # freeway capacity
    Nlaneclosed = len(scenario["lane"])
    Nlane = 5
    Cd = C * (Nlane - Nlaneclosed) / Nlane
    rho_star = min(demands[0] + 0.2*sum(demands[1:]), Cd) / vf
    print("rho*:", rho_star)
    # compute optimal cycle based on the demands, hard coding
    flowRatio = sum(demands[1:]) / (len(demands)-1) / 3600
    OptCyc = 70.5*np.log(16/(1-flowRatio)) - 171.1
    OptCycRounded = int(np.round(OptCyc * 0.1) * 10)
    TLC_mode = 0
    print("optimal cycle length (s):", OptCycRounded)
    

    # initialze measurements and control variables
    speed = [0.0] * Nsec
    density = [0.0] * Nsec
    flow = [0.0] * Nsec
    err_accum = [0.0] * Nsec
    vsl = [vf] * Nsec    
    
    rampFlow = {}
    rampQue = {}
    rmRate = {}
    Nonramp = 0
    for key in ramps:
        rampFlow[key] = 0.0
        rampQue[key] = 0.0
        rmRate[key] = 0.0
        if ramps[key]["TYPE"] == "ON": Nonramp += 1

    # initialze a csv file to record flow, density and queue length
    ctrlnum = 1000*params["ctrlmodes"][0] + 100*params["ctrlmodes"][1] + 10*params["ctrlmodes"][2] + params["ctrlmodes"][3]
    FDQfileName = os.path.join(params["respath"], "D{0}A{1}_S{2}C{3}.csv".format(demands[0], sum(demands[1:]), params["scenario"], ctrlnum))
    FDQfile = open(FDQfileName, 'a', newline='')
    FDQwriter = csv.writer(FDQfile)
    
            
    # start VISSIM simulation 
    ProgID = "VISSIM.Vissim.1000"
    networkFileName = "Corflo_Roadnet.inpx"
    layoutFileName = "Corflo_Roadnet.layx"
    try:
        print("Client: Creating a Vissim instance")
        vs = com.Dispatch(ProgID)
    
        print("Client: read network and layout")
        vs.LoadNet(os.path.join(params["filepath"], networkFileName), 0)
        vs.LoadLayout(os.path.join(params["filepath"], layoutFileName))
    
        print("Client: Setting simulations")
        vs.Simulation.SetAttValue('SimRes', simResolution)
        vs.Simulation.SetAttValue('UseMaxSimSpeed', True)        
        vs.Simulation.SetAttValue('RandSeed', randseed)
        #vs.Simulation.SetAttValue('SimSpeed', 2.0)
        #vs.Presentation.SetAttValue('RecordAnimations', True)
        
        net = vs.Net        # Reference to road network
        dataCollections = net.DataCollectionMeasurements
        links = net.Links
        vehs = net.Vehicles
        signalControllers = net.SignalControllers
        queueCounters = net.QueueCounters
        queue = [0.0] * Nonramp
        Nintersec = (queueCounters.count - Nonramp) // 4
        queueIntersec = [0.0] * Nintersec
        
        # write header
        header = []
        for i in range(Nsec):
            header.append("flow{}".format(i))
        for i in range(Nsec):
            header.append("den{}".format(i))
        for i in range(Nonramp):
            header.append("Qramp{}".format(i))
        for i in range(Nintersec):
            header.append("Qintersec{}".format(i))
        FDQwriter.writerow(header)
        
        # set vehicle inputs
        vehInputs = net.VehicleInputs
        for i in range(vehInputs.Count):
            vehInput = vehInputs.ItemByKey(1+i)
            vehInput.SetAttValue('Volume(1)', demands[i])
        
            
        # set default speed limit and 1st sign location
        VSLs = net.DesSpeedDecisions        
        for VSL in VSLs:
            VSL.SetAttValue("DesSpeedDistr(10)", vf)   # car (km/h)
            VSL.SetAttValue("DesSpeedDistr(20)", vf)   # HGV (km/h)
        
        pos = 4001 - params["L0"]*1000
        for i in link_groups[0]["VSL"]:
            VSLs.ItemByKey(i).SetAttValue("Pos", pos)
        
        # set active duration for VSL signs after the bottleneck, hard code
        # disabled in free flow scenarios
        for vslID in range(51, 56):
            VSL = VSLs.ItemByKey(vslID)
            VSL.SetAttValue("TimeFrom", startTime_sec)
            VSL.SetAttValue("TimeTo", startTime_sec+1)
        
        # Make sure that all lanes are open      
        link_Nlane = {} 
        link_length = {}
        for link in links:
            ID = link.AttValue('No')
            link_Nlane[ID] = link.AttValue('NumLanes')
            link_length[ID] = link.AttValue('Length2D')
            for lane in link.Lanes:
                lane.SetAttValue('BlockedVehClasses', '')
                 
        # compute the length of each section
        section_length = [0.0] * Nsec
        for iSec in range(len(link_groups)): 
            for linkID in link_groups[iSec]['MAINLINE']:
                link = links.ItemByKey(linkID)
                section_length[iSec] += link.AttValue('Length2D') #meter

        # initialize VSL parameters
        incidentSection = 0
        for group in link_groups:
            if scenario["link"] in group["MAINLINE"]:
                incidentSection = link_groups.index(group)
        params_VSL = {
            "mode": params["ctrlmodes"][0],
            "C": C,
            "Cd": Cd,
            "vf": vf,
            "rho_star": rho_star,
            "scenario": scenario,
            "link_groups": link_groups,
            "perturbations": params["perturbations"],
            "sec_len": section_length,
            "min_values": [30, 70],
            "start_end": [0, incidentSection],
            "w": 30
        }
        
        # use static buses to block the lanes and create the incident
        busNo = 2 * len(scenario["lane"]) 
        busArray = [0] * busNo
        
        # Construct the dictionary of ramp meters
        meters_obj = {}
        for key in ramps.keys():
            ramp = ramps[key]
            if ramp['TYPE'] == 'ON':
                meters_obj[key] = RM.RampMeter(ramp['LINK_GROUP'], vehInputs.ItemByKey(ramp['INPUT']), dataCollections.ItemByKey(ramp['DC']), signalControllers.ItemByKey(ramp['SC']), queueCounters.ItemByKey(ramp['QC']), ramp['Qcap'], rho_star)
                if params["ctrlmodes"][2]:
                    rmRate[key] = max(meters_obj[key].INPUT.AttValue('Volume(1)'), 400)
                else:
                    rmRate[key] = 1800
                meters_obj[key].update_rate(rmRate[key])
                
        
        # start simulation
        print("Client: Starting simulation")
        print("Scenario: {0}   Control: {1}   Random Seed: {2}".format(params["scenario"], ctrlnum, randseed))
        
        # Apply arterial traffic light control
        if params["ctrlmodes"][3]:
            if OptCycRounded <= 60:
                TLC_mode = 1
            util.Arterial_TLC(signalControllers, TLC_mode)
        
        currentTime = 0
        while currentTime <= params["simtime"]:
            # run sigle step of simulation
            vs.Simulation.RunSingleStep()
            currentTime = vs.Simulation.AttValue('SimSec')
            for key in meters_obj:
                meters_obj[key].meter_step(stepTime_sec)

            if 0 == currentTime % Tdata_sec:
                # Get density, flow, speed of each section
                for iSection in range(Nsec):
                    dataCollection = dataCollections.ItemByKey(link_groups[iSection]['DC'])                    
                    flow[iSection] = (3600/Tdata_sec)*dataCollection.AttValue('Vehs(Current,Last,All)')
                    Nveh = 0
                    dist = 0
                    denomenator = 0
                    for linkID in link_groups[iSection]['MAINLINE']:
                        link = links.ItemByKey(linkID)
                        link_vehs = link.Vehs
                        num_vehs = link_vehs.Count
                        sum_speed = 0
                        if num_vehs == 0:
                            v = 0
                        else:
                            for link_veh in link_vehs:
                                sum_speed += link_veh.AttValue('Speed')
                            v = sum_speed / num_vehs
                        Nveh += link_vehs.Count
                        dist += v * link_vehs.Count #total distance traveled by all vehicles in one link
                        denomenator += link_length[linkID]
                    density[iSection] = 1000 * Nveh / denomenator #number of vehicles per km
                    if 0 == Nveh:
                        speed[iSection] = 0
                    else:
                        speed[iSection] = dist / Nveh #km/h

                for i in range(len(queue)):
                    queue[i] = queueCounters.ItemByKey(i+1).AttValue('QLen(Current,Last)')
                # compute average queue length of 4 directions for each intersection
                for i in range(Nintersec):
                    st_idx = 4 * i + Nonramp + 1
                    q1 = queueCounters.ItemByKey(st_idx).AttValue('QLen(Current,Last)')
                    q2 = queueCounters.ItemByKey(st_idx+1).AttValue('QLen(Current,Last)')
                    q3 = queueCounters.ItemByKey(st_idx+2).AttValue('QLen(Current,Last)')
                    q4 = queueCounters.ItemByKey(st_idx+3).AttValue('QLen(Current,Last)')
                    queueIntersec[i] = (q1 + q2 + q3 + q4) / 4
                
                # write csv file
                row = flow + density + queue + queueIntersec
                FDQwriter.writerow(row)
                
                for key in ramps.keys():
                    dataCollection = dataCollections.ItemByKey(ramps[key]['DC'])
                    rampFlow[key] = (3600/Tdata_sec)*dataCollection.AttValue('Vehs(Current,Last,All)')
                    
            if currentTime == startTime_sec:
                # set incident scenario
                i = 0
                for lane in scenario['lane']:
                    busArray[i] = vehs.AddVehicleAtLinkPosition(300, scenario['link'], lane, scenario['coordinate'], 0, 0)
                    busArray[i+1] = vehs.AddVehicleAtLinkPosition(300, scenario['link'], lane, scenario['coordinate']+20, 0, 0)
                    i += 2
                
                # Apply Lane change control
                if params["ctrlmodes"][1]:
                    util.LC_Control(scenario, links, link_groups, params["dLC"], params["ctrlmodes"][1])
                
                

            if currentTime == endTime_sec:
                # Remove the incident
                for veh in busArray:
                    vehs.RemoveVehicle(veh.AttValue('No'))

                # open all closed lanes
                for link in links:
                    for lane in link.Lanes:
                        lane.SetAttValue('BlockedVehClasses', '')
            
            
            #Compute the VSL command and the RM rate
            if 0 == currentTime % Tctrl_sec:
                
                if params["ctrlmodes"][2]:
                    for key in meters_obj.keys():
                        rampQue[key] = meters_obj[key].QC.AttValue('QLen(Current,Last)')
                        ramp = meters_obj[key]
                        rmRate[key] = util.AlineaQ(rmRate[key], density[ramp.LINK_GROUP], rampQue[key], meters_obj[key].INPUT.AttValue('Volume(1)'), meters_obj[key].Qcap, meters_obj[key].RHO)
                        ramp.update_rate(rmRate[key])
                
                if startTime_sec <= currentTime < endTime_sec:
                    if 0 < params["ctrlmodes"][0] < 9:
                        vsl, err_accum = util.VSL_FBL(params_VSL, density, flow, vsl, err_accum, rampFlow)
                    else: 
                        vsl = util.VSL_NoCtrl(params_VSL, vsl)
                else:
                    vsl = vf*np.ones(np.shape(vsl))

                #Update VSL command in VISSIM
                for iSec in range(Nsec): 
                    for vslID in link_groups[iSec]["VSL"]:
                        VSL = VSLs.ItemByKey(vslID)
                        VSL.SetAttValue("DesSpeedDistr(10)", vsl[iSec])   # car
                        VSL.SetAttValue("DesSpeedDistr(20)", vsl[iSec])   # HGV

        FDQfile.close()


    except pywintypes.com_error as err:
        print("err=", err)

        '''
        Error
        The specified configuration is not defined within VISSIM.
        
        Description
        Some methods for evaulations results need a previously configuration for data collection. 
        The error occurs when requesting results that have not been previously configured.
        For example, using the GetSegmentResult() method of the ILink interface to request
        density results can end up with this error if the density has not been requested within the configuration
        ''' 
        

def setParameters():
    params = {
        "scenario": 0,
        # [VSL, LC, RM, TLC];  0: inactive;  1: basic version;  2+: variations
        "ctrlmodes": [0, 0, 0, 1],
        # vehicle inputs: [freeway entrance, arterial 1, arterial 2, ...]
        "demands": [9000, 800, 800, 800, 100, 800, 800, 800, 800, 800, 800, 800, 800, 800, 300, 1200, 800],
        # uncertainties in measurements and model parameters: [mainstream flow, mainstream density, w, queue length]
        "perturbations": [0.0, 0.0, 0.0, 0.0],
        # distance between first two VSL signs / length of section 0, must be in [0 , 4] (km)
        "L0": 2.0,
        # desired distance to apply LC recommendation (m)
        "dLC": 800,
        # simulation time (sec)
        "simtime": 2400,
        # number of random simulations to be run
        "simnum": 4,
        # path of VISSIM files
        "filepath": os.path.abspath(os.curdir),
        # path of result files
        "respath": os.path.join(os.path.abspath(os.curdir), 'MicroSimResults/FreeFlow')
    }
    
    return params

                    

if __name__ == '__main__':
    params = setParameters()
    util.mkdir(params["respath"])
    for i in range(params["simnum"]):
        runSimulation(params)







    
